"""
大纲服务
处理大纲导入和分集生成
"""
import json
from typing import List, Optional
from loguru import logger

from storyforge.models import Episode, EpisodeBeats, StoryBible
from storyforge.providers import LLMProviderFactory
from storyforge.config import get_config, get_provider_config
from storyforge.utils import (
    get_project_dir, save_json, load_json, save_text, load_text,
    generate_id, log_cost
)
from storyforge.utils.prompts import load_prompt


class OutlineService:
    """大纲服务"""
    
    def __init__(self):
        """初始化服务"""
        self.config = get_config()
    
    def import_outline(self, project_name: str, outline_file: str) -> str:
        """
        导入大纲文件
        
        Args:
            project_name: 项目名称
            outline_file: 大纲文件路径
            
        Returns:
            导入的大纲内容
        """
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        outline_content = load_text(outline_file)
        
        if not outline_content:
            raise FileNotFoundError(f"大纲文件不存在或为空: {outline_file}")
        
        # 保存到项目目录
        outline_path = project_dir / "outline.md"
        save_text(outline_content, str(outline_path))
        
        logger.info(f"已导入大纲: {outline_file} -> {outline_path}")
        return outline_content
    
    def identify_genre(
        self,
        outline_content: str,
        provider_name: Optional[str] = None
    ) -> dict:
        """
        自动识别大纲的题材类型
        
        Args:
            outline_content: 大纲内容
            provider_name: 提供商名称（可选）
            
        Returns:
            识别结果字典，包含 genre, confidence, reasoning, keywords
        """
        provider_name = provider_name or self.config.defaultProvider.llm
        if not get_provider_config(provider_name):
            raise ValueError(f"提供商配置不存在: {provider_name}")
        
        provider = LLMProviderFactory.create_provider(provider_name)
        
        logger.info("开始识别题材...")
        
        # 加载题材识别提示词
        prompt = load_prompt(
            role="genre_classifier",
            context={"outline_content": outline_content},
            prompts_dir=self.config.paths.prompts,
            prompt_set="default"
        )
        
        messages = [
            {"role": "user", "content": prompt}
        ]
        
        # 调用LLM识别题材（不限制输出tokens，让模型自行决定）
        # 使用配置中的温度，默认为 0.1 (json_extraction)
        temp = self.config.llm.temperatures.get("json_extraction", 0.1)
        result = provider.generate(
            messages=messages,
            temperature=temp,
            response_format={"type": "json_object"}
        )
        
        # 解析结果
        genre_info = json.loads(result) if isinstance(result, str) else result
        
        # 验证必要字段
        if "genre" not in genre_info:
            logger.warning("题材识别结果缺少 genre 字段，使用默认值")
            genre_info["genre"] = "default"
        
        # 确保 genre 是有效值
        valid_genres = ["military", "romance", "fantasy", "default"]
        if genre_info["genre"] not in valid_genres:
            logger.warning(f"无效的题材类型: {genre_info['genre']}，使用 default")
            genre_info["genre"] = "default"
        
        logger.info(f"题材识别完成: {genre_info['genre']} (置信度: {genre_info.get('confidence', 0):.2f})")
        logger.info(f"识别理由: {genre_info.get('reasoning', '无')}")
        
        return genre_info
    
    def generate_detailed_outline(
        self,
        project_name: str,
        target_episodes: int = 100,
        provider_name: Optional[str] = None,
        prompt_set: str = "default",
        pipeline: str = "single"
    ) -> dict:
        """
        基于粗粒度大纲生成详细大纲
        
        Args:
            project_name: 项目名称
            target_episodes: 目标集数
            provider_name: 提供商名称，None使用默认
            prompt_set: 提示词集名称
            pipeline: 流水线模式（single|multi-role）
            
        Returns:
            详细大纲字典
        """
        # 根据模式选择生成方法
        if pipeline == "multi-role":
            logger.info(f"开始多角色协作生成详细大纲，项目: {project_name}，目标集数: {target_episodes}")
            return self.generate_detailed_outline_multi_role(
                project_name=project_name,
                target_episodes=target_episodes
            )
        
        # 单模型模式（原有实现）
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 读取粗粒度大纲
        rough_outline_path = project_dir / "outline.md"
        if not rough_outline_path.exists():
            raise FileNotFoundError(f"粗粒度大纲文件不存在: {rough_outline_path}")
        
        rough_outline = load_text(str(rough_outline_path))
        if not rough_outline:
            raise ValueError(f"粗粒度大纲文件为空: {rough_outline_path}")
        
        provider_name = provider_name or self.config.defaultProvider.llm
        
        if not get_provider_config(provider_name):
            raise ValueError(f"提供商配置不存在: {provider_name}")
        
        provider = LLMProviderFactory.create_provider(provider_name)
        
        logger.info(f"开始生成详细大纲，项目: {project_name}，目标集数: {target_episodes}")
        
        # 加载并渲染提示词模板（system + user）
        system_prompt = load_prompt(
            role="system_outline_expander",
            context={},
            prompts_dir=self.config.paths.prompts,
            prompt_set=prompt_set
        )
        user_prompt = load_prompt(
            role="outline_expander",
            context={
                "rough_outline": rough_outline,
                "target_episodes": target_episodes
            },
            prompts_dir=self.config.paths.prompts,
            prompt_set=prompt_set
        )
        
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
        
        # 不限制输出tokens，让模型根据输入自行决定输出长度
        logger.info(f"调用 LLM 生成详细大纲 (provider: {provider_name}, 不限制输出tokens)")
        
        # 使用配置中的温度，默认为 0.7 (outline)
        temp = self.config.llm.temperatures.get("outline", 0.7)
        result = provider.generate(
            messages=messages,
            temperature=temp,
            response_format={"type": "json_object"}
        )
        
        detailed_outline = json.loads(result) if isinstance(result, str) else result
        
        # 验证必要字段
        required_fields = ["title", "genre", "background", "mainPlot", "characters"]
        for field in required_fields:
            if field not in detailed_outline:
                logger.warning(f"详细大纲缺少字段: {field}")
        
        # 保存详细大纲
        detailed_outline_path = project_dir / "detailed_outline.json"
        save_json(detailed_outline, str(detailed_outline_path))
        
        # 同时保存为 Markdown 格式（便于阅读）
        detailed_outline_md = self._format_detailed_outline_markdown(detailed_outline)
        detailed_outline_md_path = project_dir / "detailed_outline.md"
        save_text(detailed_outline_md, str(detailed_outline_md_path))
        
        logger.info(f"详细大纲已生成: {detailed_outline_path}")
        
        return detailed_outline
    
    def generate_detailed_outline_multi_role(
        self,
        project_name: str,
        target_episodes: int = 100,
        story_bible: Optional[StoryBible] = None
    ) -> dict:
        """
        多角色模式生成详细大纲
        
        Args:
            project_name: 项目名称
            target_episodes: 目标集数
            story_bible: 故事事实库（可选）
            
        Returns:
            详细大纲字典
        """
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 读取粗粒度大纲
        rough_outline_path = project_dir / "outline.md"
        if not rough_outline_path.exists():
            logger.error(f"粗粒度大纲文件不存在: {rough_outline_path}")
            raise FileNotFoundError(f"粗粒度大纲文件不存在: {rough_outline_path}")
        
        rough_outline = load_text(str(rough_outline_path))
        if not rough_outline:
            logger.error(f"粗粒度大纲文件为空: {rough_outline_path}")
            raise ValueError(f"粗粒度大纲文件为空: {rough_outline_path}")
        
        logger.info(f"开始多角色协作生成详细大纲，项目: {project_name}，目标集数: {target_episodes}")
        
        # 调用多角色流水线
        from storyforge.orchestrator import MultiRolePipeline
        
        pipeline = MultiRolePipeline()
        detailed_outline = pipeline.generate_detailed_outline(
            project_name=project_name,
            rough_outline=rough_outline,
            target_episodes=target_episodes,
            story_bible=story_bible
        )
        
        # 保存详细大纲
        detailed_outline_path = project_dir / "detailed_outline.json"
        save_json(detailed_outline, str(detailed_outline_path))
        
        # 同时保存为 Markdown 格式（便于阅读）
        detailed_outline_md = self._format_detailed_outline_markdown(detailed_outline)
        detailed_outline_md_path = project_dir / "detailed_outline.md"
        save_text(detailed_outline_md, str(detailed_outline_md_path))
        
        logger.info(f"多角色协作完成，详细大纲已生成: {detailed_outline_path}")
        
        return detailed_outline
    
    def _format_detailed_outline_markdown(self, outline_dict: dict) -> str:
        """
        将详细大纲字典格式化为 Markdown 格式
        
        Args:
            outline_dict: 详细大纲字典
            
        Returns:
            Markdown 格式的字符串
        """
        md_lines = []
        
        # 标题
        if "title" in outline_dict:
            md_lines.append(f"# {outline_dict['title']}")
            md_lines.append("")
        
        # 题材类型
        if "genre" in outline_dict:
            md_lines.append("## 题材类型")
            for genre in outline_dict["genre"]:
                md_lines.append(f"- {genre}")
            md_lines.append("")
        
        # 卖点
        if "sellingPoints" in outline_dict:
            md_lines.append("## 卖点")
            for i, point in enumerate(outline_dict["sellingPoints"], 1):
                md_lines.append(f"{i}. {point}")
            md_lines.append("")
        
        # 目标集数
        if "targetEpisodes" in outline_dict:
            md_lines.append(f"**目标集数**: {outline_dict['targetEpisodes']}集")
            md_lines.append("")
        
        # 世界观设定
        if "worldView" in outline_dict:
            world_view = outline_dict["worldView"]
            md_lines.append("## 世界观设定")
            md_lines.append("")
            
            if "era" in world_view:
                md_lines.append(f"### 时代背景")
                md_lines.append(world_view["era"])
                md_lines.append("")
            
            if "location" in world_view:
                md_lines.append(f"### 主要地点")
                md_lines.append(world_view["location"])
                md_lines.append("")
            
            if "historicalContext" in world_view:
                md_lines.append(f"### 历史背景")
                md_lines.append(world_view["historicalContext"])
                md_lines.append("")
            
            if "technologyLevel" in world_view:
                md_lines.append(f"### 科技水平")
                md_lines.append(world_view["technologyLevel"])
                md_lines.append("")
            
            if "socialStructure" in world_view:
                md_lines.append(f"### 社会结构")
                md_lines.append(world_view["socialStructure"])
                md_lines.append("")
        elif "background" in outline_dict:
            # 兼容旧版本的 background 字段
            md_lines.append("## 故事背景")
            md_lines.append(outline_dict["background"])
            md_lines.append("")
        
        # 主要人物
        if "characters" in outline_dict:
            md_lines.append("## 主要人物")
            md_lines.append("")
            for char in outline_dict["characters"]:
                name = char.get('name', '未知')
                role = char.get('role', '未知角色')
                age = char.get('age', '')
                age_str = f"，{age}岁" if age else ""
                
                md_lines.append(f"### {name} ({role}{age_str})")
                md_lines.append("")
                
                if "background" in char:
                    md_lines.append(f"**背景**: {char['background']}")
                    md_lines.append("")
                elif "description" in char:
                    # 兼容旧版本
                    md_lines.append(char["description"])
                    md_lines.append("")
                
                if "personality" in char:
                    md_lines.append(f"**性格**: {char['personality']}")
                    md_lines.append("")
                
                if "abilities" in char:
                    md_lines.append(f"**能力**: {char['abilities']}")
                    md_lines.append("")
                
                if "goals" in char:
                    md_lines.append(f"**目标**: {char['goals']}")
                    md_lines.append("")
                
                if "conflicts" in char:
                    md_lines.append(f"**冲突**: {char['conflicts']}")
                    md_lines.append("")
                
                if "characterArc" in char:
                    md_lines.append(f"**角色弧光**: {char['characterArc']}")
                    md_lines.append("")
                
                if "relationships" in char:
                    md_lines.append(f"**关系**: {char['relationships']}")
                    md_lines.append("")
        
        # 剧情阶段
        if "plotStages" in outline_dict:
            md_lines.append("## 剧情阶段")
            md_lines.append("")
            for stage in outline_dict["plotStages"]:
                stage_num = stage.get('stage', '')
                name = stage.get('name', '未命名阶段')
                episodes = stage.get('episodes', '')
                
                md_lines.append(f"### 阶段{stage_num}：{name} ({episodes})")
                md_lines.append("")
                
                if "objective" in stage:
                    md_lines.append(f"**目标**: {stage['objective']}")
                    md_lines.append("")
                
                if "summary" in stage:
                    md_lines.append(f"**概述**: {stage['summary']}")
                    md_lines.append("")
                
                if "keyEvents" in stage:
                    md_lines.append("**关键事件**:")
                    for event in stage["keyEvents"]:
                        md_lines.append(f"- {event}")
                    md_lines.append("")
                
                if "climax" in stage:
                    md_lines.append(f"**高潮**: {stage['climax']}")
                    md_lines.append("")
        
        # 主线剧情
        if "mainPlot" in outline_dict:
            md_lines.append("## 主线剧情总览")
            md_lines.append("")
            md_lines.append(outline_dict["mainPlot"])
            md_lines.append("")
        
        # 关键冲突
        if "keyConflicts" in outline_dict:
            md_lines.append("## 关键冲突")
            md_lines.append("")
            conflicts = outline_dict["keyConflicts"]
            
            # 检查是否为新版本格式（字典数组）
            if conflicts and isinstance(conflicts[0], dict):
                for conflict in conflicts:
                    conflict_type = conflict.get('type', '冲突')
                    description = conflict.get('description', '')
                    md_lines.append(f"### {conflict_type}")
                    md_lines.append(description)
                    md_lines.append("")
            else:
                # 兼容旧版本格式（字符串数组）
                for conflict in conflicts:
                    md_lines.append(f"- {conflict}")
                md_lines.append("")
        
        # 转折点
        if "turningPoints" in outline_dict:
            md_lines.append("## 转折点")
            md_lines.append("")
            turning_points = outline_dict["turningPoints"]
            
            # 检查是否为新版本格式（字典数组）
            if turning_points and isinstance(turning_points[0], dict):
                for tp in turning_points:
                    episode = tp.get('episode', '')
                    event = tp.get('event', '')
                    impact = tp.get('impact', '')
                    emotion = tp.get('emotionalCurve', '')
                    
                    md_lines.append(f"### 第{episode}集：{event}")
                    if impact:
                        md_lines.append(f"**影响**: {impact}")
                    if emotion:
                        md_lines.append(f"**情感曲线**: {emotion}")
                    md_lines.append("")
            else:
                # 兼容旧版本格式（字符串数组）
                for turning_point in turning_points:
                    md_lines.append(f"- {turning_point}")
                md_lines.append("")
        
        # 主题
        if "themes" in outline_dict:
            md_lines.append("## 主题")
            for theme in outline_dict["themes"]:
                md_lines.append(f"- {theme}")
            md_lines.append("")
        
        # 风格指南
        if "styleGuide" in outline_dict:
            style_guide = outline_dict["styleGuide"]
            md_lines.append("## 风格指南")
            md_lines.append("")
            
            if "pacing" in style_guide:
                md_lines.append(f"**节奏**: {style_guide['pacing']}")
            if "tone" in style_guide:
                md_lines.append(f"**基调**: {style_guide['tone']}")
            if "visualStyle" in style_guide:
                md_lines.append(f"**视觉风格**: {style_guide['visualStyle']}")
            if "narrativeStyle" in style_guide:
                md_lines.append(f"**叙事风格**: {style_guide['narrativeStyle']}")
            md_lines.append("")
        else:
            # 兼容旧版本
            if "pacing" in outline_dict:
                md_lines.append(f"**节奏风格**: {outline_dict['pacing']}")
            if "style" in outline_dict:
                md_lines.append(f"**风格定位**: {outline_dict['style']}")
            if "pacing" in outline_dict or "style" in outline_dict:
                md_lines.append("")
        
        # 目标受众
        if "targetAudience" in outline_dict:
            md_lines.append(f"**目标受众**: {outline_dict['targetAudience']}")
            md_lines.append("")
        
        # 制作建议
        if "productionNotes" in outline_dict:
            md_lines.append("## 制作建议")
            md_lines.append("")
            md_lines.append(outline_dict["productionNotes"])
            md_lines.append("")
        
        return "\n".join(md_lines)
    
    def _load_outline_content(self, project_name: str) -> str:
        """
        加载大纲内容（强制使用小说正文）
        
        分集生成必须基于小说正文，不再支持直接从大纲生成
        
        Args:
            project_name: 项目名称
            
        Returns:
            小说正文内容
            
        Raises:
            FileNotFoundError: 小说正文不存在
        """
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 必须使用小说正文（分集生成的前置要求）
        novel_md_path = project_dir / "novel.md"
        novel_json_path = project_dir / "novel.json"
        
        if novel_md_path.exists():
            logger.info(f"✅ 使用小说正文生成分集: {novel_md_path}")
            return load_text(str(novel_md_path))
        elif novel_json_path.exists():
            logger.info(f"✅ 使用小说正文生成分集: {novel_json_path}")
            # 从JSON加载并格式化为文本
            novel_data = load_json(str(novel_json_path))
            chapters_text = []
            for chapter in novel_data.get("chapters", []):
                chapters_text.append(f"## {chapter.get('title', '未命名章节')}\n\n{chapter.get('content', '')}")
            return "\n\n".join(chapters_text)
        
        raise FileNotFoundError(
            f"\n❌ 错误：小说正文不存在！\n\n"
            f"分集生成需要基于小说正文进行，请先运行以下命令生成小说：\n"
            f"  python -m storyforge novel generate --project {project_name}\n\n"
            f"或使用多角色模式：\n"
            f"  python -m storyforge novel generate --project {project_name} --pipeline multi-role\n"
        )
    
    def generate_episodes_single_model(
        self,
        project_name: str,
        outline: Optional[str] = None,
        count: int = 100,
        provider_name: Optional[str] = None
    ) -> List[Episode]:
        """
        单模型模式生成分集
        
        Args:
            project_name: 项目名称
            outline: 大纲内容（可选，如果为None则自动加载）
            count: 剧集数量
            provider_name: 提供商名称，None使用默认
            
        Returns:
            Episode列表
        """
        # 如果没有提供大纲，自动加载
        if outline is None:
            outline = self._load_outline_content(project_name)
        
        provider_name = provider_name or self.config.defaultProvider.llm
        
        if not get_provider_config(provider_name):
            raise ValueError(f"提供商配置不存在: {provider_name}")
        
        provider = LLMProviderFactory.create_provider(provider_name)
        
        logger.info(f"使用单模型模式生成 {count} 集，提供商: {provider_name}")
        
        episodes = []
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 加载详细大纲（JSON，供模板使用）
        detailed_outline_json = {}
        try:
            detailed_outline_path = project_dir / "detailed_outline.json"
            if detailed_outline_path.exists():
                with open(detailed_outline_path, 'r', encoding='utf-8') as f:
                    detailed_outline_json = json.load(f)
        except Exception:
            detailed_outline_json = {}

        # 加载小说正文（可选，若存在则注入提示词）
        novel_text = ""
        try:
            novel_md_path = project_dir / "novel.md"
            if novel_md_path.exists():
                novel_text = load_text(str(novel_md_path)) or ""
        except Exception:
            novel_text = ""
        
        # 续写索引：在现有最大 episode_*.json 基础上继续
        import os, re
        max_index = 0
        try:
            for name in os.listdir(project_dir):
                m = re.match(r"episode_(\d+)\.json$", name)
                if m:
                    idx = int(m.group(1))
                    if idx > max_index:
                        max_index = idx
        except Exception:
            max_index = 0
        
        start_index = max_index + 1
        total_episodes = start_index + count - 1
        
        def get_act_phase(ep_idx: int, total: int) -> str:
            pos = ep_idx / max(total, 1)
            if pos <= 0.15:
                return "act1"
            if 0.45 < pos < 0.55:
                return "midpoint"
            if pos <= 0.45:
                return "rising"
            if pos <= 0.75:
                return "setback"
            if pos <= 0.9:
                return "rebuild"
            return "finale"
        
        for offset in range(count):
            idx = start_index + offset
            try:
                # 构建提示词（高质量单集模板）
                act_phase = get_act_phase(idx, total_episodes)
                
                # 读取时长配置
                first_minutes = 5
                default_minutes = 3
                min_seconds = 120
                max_seconds = 300
                
                try:
                    if hasattr(self.config, 'generation') and self.config.generation:
                        gen_config = self.config.generation
                        first_minutes = int(getattr(gen_config, 'firstEpisodeMinutes', 5))
                        default_minutes = int(getattr(gen_config, 'defaultEpisodeMinutes', 3))
                        min_seconds = int(getattr(gen_config, 'minEpisodeSeconds', 120))
                        max_seconds = int(getattr(gen_config, 'maxEpisodeSeconds', 300))
                except Exception:
                    pass  # 使用默认值
                
                is_first = (idx == 1)
                # 第一集使用特定时长，其他集使用默认时长
                target_minutes = first_minutes if is_first else default_minutes
                target_duration = target_minutes * 60

                user_prompt = load_prompt(
                    role="episode_generator",
                    context={
                        "detailed_outline": detailed_outline_json,
                        "outline": outline,
                        "novel_text": novel_text,
                        "episode_index": idx,
                        "total_episodes": total_episodes,
                        "act_phase": act_phase,
                        "is_first_episode": str(is_first).lower(),
                        "target_minutes": target_minutes,
                        "target_duration": target_duration,
                        "min_seconds": min_seconds,
                        "max_seconds": max_seconds
                    },
                    prompts_dir=self.config.paths.prompts,
                    prompt_set="default"
                )
                
                system_prompt = load_prompt(
                    role="system_episode_generator",
                    context={},
                    prompts_dir=self.config.paths.prompts,
                    prompt_set="default"
                )

                messages = [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt}
                ]
                
                # 不限制输出tokens，让模型自行决定
                # 使用配置中的温度，默认为 0.5 (script)
                temp = self.config.llm.temperatures.get("script", 0.5)
                result = provider.generate(
                    messages=messages,
                    temperature=temp,
                    response_format={"type": "json_object"}
                )
                
                episode_data = json.loads(result) if isinstance(result, str) else result
                
                episode = Episode(
                    id=generate_id(f"ep_{idx}_"),
                    projectId=project_name,
                    index=idx,
                    outline=episode_data.get("outline", ""),
                    scriptJSON={
                        "beats": episode_data.get("beats", []),
                        "scenes": episode_data.get("scenes", []),
                        "antagonistPOV": episode_data.get("antagonistPOV", False),
                        "setPieces": episode_data.get("setPieces", []),
                        "risk": episode_data.get("risk", ""),
                        "cost": episode_data.get("cost", ""),
                        "cliffhanger": episode_data.get("cliffhanger", "")
                    },
                    status="draft"
                )
                
                # 保存剧集
                episode_path = project_dir / f"episode_{idx}.json"
                save_json(episode.dict(), str(episode_path))
                
                episodes.append(episode)
                logger.info(f"第{idx}集生成完成")
                
            except Exception as e:
                logger.error(f"生成第{idx}集失败: {e}")
                continue
        
        logger.info(f"单模型模式完成，成功生成 {len(episodes)} 集")
        return episodes
    
    def generate_episodes_multi_role(
        self,
        project_name: str,
        outline: Optional[str] = None,
        count: int = 100,
        story_bible: Optional[StoryBible] = None
    ) -> List[Episode]:
        """
        多角色模式生成分集
        
        Args:
            project_name: 项目名称
            outline: 大纲内容（可选，如果为None则自动加载）
            count: 剧集数量
            story_bible: 故事事实库
            
        Returns:
            Episode列表
        """
        # 强制检查小说正文是否存在
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        novel_json_path = project_dir / "novel.json"
        novel_md_path = project_dir / "novel.md"
        
        if not novel_json_path.exists() and not novel_md_path.exists():
            raise FileNotFoundError(
                f"\n❌ 错误：小说正文不存在！\n\n"
                f"分集生成需要基于小说正文进行，请先运行以下命令生成小说：\n"
                f"  python -m storyforge novel generate --project {project_name}\n\n"
                f"或使用多角色模式：\n"
                f"  python -m storyforge novel generate --project {project_name} --pipeline multi-role\n"
            )
        
        logger.info(f"✅ 检测到小说正文: {novel_json_path if novel_json_path.exists() else novel_md_path}")
        
        # 如果没有提供大纲，自动加载（优先使用小说正文）
        if outline is None:
            outline = self._load_outline_content(project_name)
        
        from storyforge.orchestrator import MultiRolePipeline
        
        pipeline = MultiRolePipeline()
        episode_beats_list = pipeline.generate_episodes(
            project_name=project_name,
            outline=outline,
            count=count,
            story_bible=story_bible
        )
        
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        episodes = []
        
        for beats in episode_beats_list:
            try:
                episode = Episode(
                    id=generate_id(f"ep_{beats.episodeIndex}_"),
                    projectId=project_name,
                    index=beats.episodeIndex,
                    outline=f"第{beats.episodeIndex}集",
                    scriptJSON={
                        "beats": [beat.dict() for beat in beats.beats]
                    },
                    status="draft"
                )
                
                episode_path = project_dir / f"episode_{beats.episodeIndex}.json"
                save_json(episode.dict(), str(episode_path))
                
                episodes.append(episode)
                
            except Exception as e:
                logger.error(f"处理剧集节拍失败: {e}")
                continue
        
        return episodes

